package datastructure.threadpool;
import java.util.Objects;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

public class PowerThreadPoolExecutor implements Executor {

    /**
     * 核心线程数
     */
    private final int coreSize;

    /**
     * 最大线程数
     */
    private final int maxSize;

    /**
     * 临时线程的保活时间
     */
    private final int keepAliveTime;

    /**
     * 临时线程的保活时间的时间单位
     */
    private final TimeUnit timeUnit;

    /**
     * 任务队列
     */
    private final BlockingQueue<Runnable> workQueue;

    /**
     * 线程工厂
     */
    private final ThreadFactory threadFactory;

    /**
     * 记录当前线程总数
     */
    private int currentThreads = 0;

    public PowerThreadPoolExecutor(int coreSize, int maxSize, int queueSize, int keepAliveTime, TimeUnit timeUnit, ThreadFactory threadFactory) {
        this.coreSize = coreSize;
        this.maxSize = maxSize;
        this.workQueue = new ArrayBlockingQueue<>(queueSize);
        this.keepAliveTime = keepAliveTime;
        this.timeUnit = timeUnit;
        this.threadFactory = threadFactory;
    }

    @Override
    public synchronized void execute(Runnable task) { // 加锁保证创建线程时是线程安全的
        // 当前线程数小于核心线程数，创建核心线程
        if (currentThreads < coreSize) {
            System.out.println("开始创建核心线程");
            createThread(task, true);
            return;
        }
        // 当前线程数已经达到了核心线程数，将任务放到队列中
        boolean success = workQueue.offer(task);
        if (success) {
            System.out.println("已将任务放到队列中");
            // 任务已经放到队列中了，不需要创建临时线程
            return;
        }
        // 队列放满了，开始创建临时线程
        if (currentThreads < maxSize) {
            System.out.println("开始创建临时线程");
            createThread(task, false);
            return;
        }
        System.out.println("线程池的任务已经饱和，触发饱和拒绝策略");
        // 代码走到这里，说明此时线程数已经达到了最大线程数，新接收的任务则触发饱和拒绝策略，我这里默认的策略是抛异常
        throw new RejectedExecutionException("线程池的任务已经饱和");
    }

    /**
     * 创建线程
     *
     * @param task         线程池要执行的任务
     * @param isCoreThread 是否为核心线程  传true创建核心线程，传false创建临时线程
     */
    private void createThread(Runnable task, boolean isCoreThread) {
        // 线程数 + 1
        currentThreads++;
        // 创建并启动线程
        threadFactory.newThread(() -> {
            if (Objects.nonNull(task)) {
                //执行用户提交的任务
                System.out.println("线程" + Thread.currentThread().getName() + "开始执行用户提交的任务");
                task.run();
            }
            // 线程执行任务完成后，通过阻塞队列实现保活
            try {
                while (true) { // 通过死循环实现核心线程的永久保活，即便它在执行任务完成后仍然会监听阻塞队列，从而实现线程保活
                    Runnable queueTask;
                    if (isCoreThread) {
                        // 如果核心线程从队列中一直没有拿到任务，就会阻塞在这里，从而实现线程保活
                        queueTask = workQueue.take();
                    } else {
                        // 如果临时线程在等待了指定时间后还没有从队列中拿到任务，就会返回null。这里的等待时间就是临时线程的存活时间
                        queueTask = workQueue.poll(keepAliveTime, timeUnit);
                    }
                    if (Objects.isNull(queueTask)) {
                        // 说明是临时线程已经过了等待时间，而且等待的过程中没能从队列中拿到任务，则跳出循环，临时线程也就会自动销毁
                        // 如果有任务，则会继续处理，每处理一次任务，就相当于在给自己的生命续期
                        break;
                    }
                    // 执行队列中的任务
                    System.out.println("线程" + Thread.currentThread().getName() + "正在执行队列中的任务");
                    queueTask.run();
                }
            } catch (InterruptedException ignore) {
            } finally {
                // 线程执行结束后，也就随时自动销毁，线程数 - 1
                currentThreads--;
            }
        }).start();
    }
    public static void main(String[] args) {
        final AtomicInteger counter = new AtomicInteger(1);
        String threadNamePrefix = "thread";
        PowerThreadPoolExecutor powerThreadPoolExecutor = new PowerThreadPoolExecutor(2, 3, 2, 1800, TimeUnit.SECONDS, r ->
                new Thread(r, threadNamePrefix + "-" + counter.getAndIncrement())
        );
        powerThreadPoolExecutor.execute(() -> {
            System.out.println("自定义线程" + Thread.currentThread().getName() + " => 开始创建订单");
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });

    }

}
